﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Diagnostics;

namespace LowLevelGraphics.ColorSpaces
{
    /// <summary>
    /// CIEXYZ Channel
    /// </summary>
    public enum CIEXYZChannel
    {
        /// <summary>
        /// 
        /// </summary>
        X = 0,
        /// <summary>
        /// 
        /// </summary>
        Y = 1,
        /// <summary>
        /// 
        /// </summary>
        Z = 2
    }

    /// <summary>
    /// Class to convert RGB to XYZ and back
    /// </summary>
    [DebuggerDisplay("X: {m_X}, Y: {m_Y}, Z: {m_Z}")]
    public class CIEXYZ : AbstractColorSpace
    {
        protected double m_X = 0.0f;
        protected double m_Y = 0.0f;
        protected double m_Z = 0.0f;

        /// <summary>
        /// primary constructor
        /// </summary>
        /// <param name="_color"></param>
        public CIEXYZ(Color _color)
            : base(_color)
        {

        }

        /// <summary>
        /// Initializes a new instance of the <see cref="CIEXYZ"/> class.
        /// </summary>
        /// <param name="_x">The _x.</param>
        /// <param name="_y">The _y.</param>
        /// <param name="_z">The _z.</param>
        public CIEXYZ(double _x, double _y, double _z)
        {
            m_X = _x;
            m_Y = _y;
            m_Z = _z;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="CIEXYZ"/> class.
        /// </summary>
        internal CIEXYZ()
        {
        }

        /// <summary>
        /// this is used by clone to create a deep copy
        /// </summary>
        /// <param name="_xyz"></param>
        internal CIEXYZ(CIEXYZ _xyz)
        {
            this.m_Color = _xyz.m_Color;
            this.m_X = _xyz.m_X;
            this.m_Y = _xyz.m_Y;
            this.m_Z = _xyz.m_Z;
        }

        /// <summary>
        /// Enable casting into operator
        /// </summary>
        /// <param name="_color"></param>
        /// <returns></returns>
        public static explicit operator CIEXYZ(Color _color)
        {
            return new CIEXYZ(_color);
        }

        /// <summary>
        /// sets or gets y
        /// </summary>
        public double Y
        {
            get { return m_Y; }
            set { m_Y = value; }
        }

        /// <summary>
        /// sets or gets x
        /// </summary>
        public double X
        {
            get { return m_X; }
            set { m_X = value; }
        }

        /// <summary>
        /// sets or gets z
        /// </summary>
        public double Z
        {
            get { return m_Z; }
            set { m_Z = value; }
        }

        /// <summary>
        /// This is used to transform the rgb values into XYZ
        /// </summary>
        protected override void CalcValues()
        {
            double R = m_Color.R;
            double G = m_Color.G;
            double B = m_Color.B;

            double var_R = (R / 255);        //R from 0 to 255
            double var_G = (G / 255);        //G from 0 to 255
            double var_B = (B / 255);        //B from 0 to 255

            if ( var_R > 0.04045 ) var_R = Math.Pow(( ( var_R + 0.055 ) / 1.055 ), 2.4);
            else                   var_R = var_R / 12.92;
            if ( var_G > 0.04045 ) var_G = Math.Pow(( ( var_G + 0.055 ) / 1.055 ), 2.4);
            else                   var_G = var_G / 12.92;
            if ( var_B > 0.04045 ) var_B = Math.Pow(( ( var_B + 0.055 ) / 1.055 ), 2.4);
            else                   var_B = var_B / 12.92;

            var_R = var_R * 100;
            var_G = var_G * 100;
            var_B = var_B * 100;

            //Observer. = 2°, Illuminant = D65
            m_X = var_R * 0.4124 + var_G * 0.3576 + var_B * 0.1805;
            m_Y = var_R * 0.2126 + var_G * 0.7152 + var_B * 0.0722;
            m_Z = var_R * 0.0193 + var_G * 0.1192 + var_B * 0.9505;       
        }

        /// <summary>
        /// Retransform to rgb color space
        /// </summary>
        /// <returns></returns>
        public override Color ToColor()
        {
            double var_X = m_X / 100;        //X from 0 to  95.047      (Observer = 2°, Illuminant = D65)
            double var_Y = m_Y / 100;        //Y from 0 to 100.000
            double var_Z = m_Z / 100;        //Z from 0 to 108.883

            double var_R = var_X *  3.2406 + var_Y * -1.5372 + var_Z * -0.4986;
            double var_G = var_X * -0.9689 + var_Y *  1.8758 + var_Z *  0.0415;
            double var_B = var_X *  0.0557 + var_Y * -0.2040 + var_Z *  1.0570;

            if ( var_R > 0.0031308 ) var_R = 1.055 * ( Math.Pow(var_R, ( 1 / 2.4 )) ) - 0.055;
            else                     var_R = 12.92 * var_R;
            if ( var_G > 0.0031308 ) var_G = 1.055 * ( Math.Pow(var_G, ( 1 / 2.4 )) ) - 0.055;
            else                     var_G = 12.92 * var_G;
            if ( var_B > 0.0031308 ) var_B = 1.055 * ( Math.Pow(var_B, ( 1 / 2.4 )) ) - 0.055;
            else                     var_B = 12.92 * var_B;

            double R = var_R * 255;
            double G = var_G * 255;
            double B = var_B * 255;

            //TODO: XYZ, check formulas to eliminate this
            if (R < 0) R = 0; if (R > 255) R = 255;
            if (G < 0) G = 0; if (G > 255) G = 255;
            if (B < 0) B = 0; if (B > 255) B = 255;
            return Color.FromArgb((int)R, (int)G, (int)B);
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return string.Format("Color X = {0}, Y = {1}, Z = {2}", m_X, m_Y, m_Z);
        }

        /// <summary>
        /// Gets the values.
        /// </summary>
        /// <returns></returns>
        public override object[] GetValues()
        {
            return new object[] { m_X, m_Y, m_Z };
        }

        /// <summary>
        /// Sets the values.
        /// </summary>
        /// <param name="_aObject">The _a object.</param>
        public override void SetValues(object[] _aObject)
        {
            this.m_X = (double)_aObject[0];
            this.m_Y = (double)_aObject[1];
            this.m_Z = (double)_aObject[2];
        }

        /// <summary>
        /// Use this to create a deep copy
        /// </summary>
        /// <returns></returns>
        public override object Clone()
        {
            return new CIEXYZ(this);
        }
    }
}
